/*
    The MIT License

    Copyright (c) 2010 IFMO/GameDev Studio

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/

#include "fr_water.h"

#include <cutil_math.h>

#include "water_cu.h"


/*-----------------------------------------------------------------------------
	Water rendering :
-----------------------------------------------------------------------------*/

const uint	FFT_N			=	512;
const float	G				=	9.8f;

const int DROP_POOL_SIZE	=   10240;

const int	WAKE_GRID_SIZE	=	512;
const float WAKE_GRID_STEP  =   0.5f;

const float WAKE_DROP_INTENSITY  =   0.2f;
const float WAKE_DROP_POW   =   0.0f;

#define CUDA_CALL( expr ) if ((expr)!=cudaSuccess) LOG_ERROR("CUDA : %s - failed", #expr);

EFRWater *EFRWater::self	=	NULL;

//
//	EFRWater::EFRWater
//
EFRWater::EFRWater( EFRScene *scene, const EString &mesh, const EString &noise, float omega, float scale, float falloff )
{
	self	=	this;
	this->scene	=	scene;
	
	peak_frequency	=	1.0f;
	narrowness		=	16.0f;
	jonswap_gamma	=	1;

	//
	//	init CUDA :
	//
	init_cuda_fft_water();
	
	LUALIB_DECLARE("water")
	LUALIB_METHOD( set_spectra		 )
	LUALIB_METHOD( set_sine			 )
	LUALIB_METHOD( set_spectra_wind	 )
	LUALIB_METHOD( get_wave_height	 )
	LUALIB_REGISTER( CoreLua() )
	
	

	LOG_INIT("Water rendering system");
	
	rtm	=	scene->driver->GetRTM();
	txm	=	scene->driver->GetTXM();
	vbm	=	scene->driver->GetVBM();

	//	create renderable mesh :	
	//water_mesh		=	ge()->CreatePlane(2,2, 80, 80);
	water_mesh		=	ge()->LoadMeshFromFile("shipxds/models/sea.esx|sea");
	water_vb		=	vbm->CreateEmptyModel();
	water_vb->UpdateFromMesh( water_mesh );
	
	water_back_mesh	=	ge()->LoadMeshFromFile("shipxds/models/sea.esx|back");
	water_back_vb	=	vbm->CreateEmptyModel();
	water_back_vb->UpdateFromMesh( water_back_mesh );
	
	foam_tx			=	txm->RegisterTexture( "shipxds/models/ocean_foam.tga" );
	
	//
	r_sky	=	scene->AddEntity();
	r_sky->SetMesh( ge()->LoadMeshFromFile("shipxds/models/sky.esx|sky") );
	
	wavegen.time	=	0;

	ReloadShader();
	
	enabled	=	false;
}


//
//	EFRWater::SetTime
//
void EFRWater::SetTime( float time )
{
	wavegen.delta_time	=	time - wavegen.delta_time;
	wavegen.time		=	time;
}


//
//	EFRWater::ReloadShader
//
void EFRWater::ReloadShader( void )
{
	water_fx	=	scene->driver->GetFXM()->CompileEffect("shaders/water2.cgfx");
}


//
//	EFRWater::~EFRWater
//
EFRWater::~EFRWater( void )
{
	kill_cuda_fft_water();
	self	=	NULL;
	LOG_SHUTDOWN("Water rendering system");
}


//
//	EFRWater::Render
//
void EFRWater::Render( EFrame frame, EGBuffer gbuf )
{
	if (!water_fx) {
		return;
	}
	
	update_wakes();
	update_cuda_fft_water();
	
	uint sw, sh;
	scene->driver->GetScreenSize(sw, sh);
	
	IPxTexture	back_color	=	scene->rtm->AllocRenderTarget( RS_FORMAT_ARGB_U8, sw/2, sh/2 );
	IPxTexture	back_depth	=	gbuf.ds;
	
	scene->rtm->CopyColorFrameData( back_color, frame.color, true );
	scene->rtm->SetFrameBuffer( frame.depth, frame.color );

	EMatrix	w	=	EMatrix();
	EMatrix	v	=	scene->view.matrix_view;
	EMatrix	p	=	scene->proj.matrix_proj;
	

	water_fx->SetUniform( "matrix_wvp",			w * v * p );
	water_fx->SetUniform( "matrix_vp_i",		( v * p ).Inverse() );
	water_fx->SetUniform( "time",				wavegen.time );
	water_fx->SetUniform( "spatial_period",		spatial_period );
	water_fx->SetTexture( "sm_reflection",		txm->RegisterTexture("textures/ocean_reflection.tga") );
	water_fx->SetTexture( "sm_wave_grid",		waves_tex );
	water_fx->SetTexture( "sm_foam",			foam_tx );
	
	water_fx->SetTexture( "sm_back_color",		back_color );
	water_fx->SetTexture( "sm_back_depth",		back_depth );
	
	water_fx->SetUniform( "view_pos",			scene->view.position );

	water_fx->SetTexture( "sm_wake_grid0",		wakes_tex[1-cur_wake_buf] );
	water_fx->SetTexture( "sm_wake_grid1",		wakes_tex[cur_wake_buf] );
	water_fx->SetUniform( "wake_spatial_period", WAKE_GRID_SIZE * WAKE_GRID_STEP );
	water_fx->SetUniform( "wake_center", wake_center);
	water_fx->SetUniform( "wake_grid_size_inv", 1.0f / WAKE_GRID_SIZE);


	uint n;
	
	water_fx->SetTechnique("tech_water");
	water_fx->Begin(n);
	for (uint i=0; i<n; i++) {
		water_fx->BeginPass(i);
		water_vb->DrawAllTris();
		water_fx->EndPass();
	}
	water_fx->End();

	water_fx->SetTechnique("tech_water_back");
	water_fx->Begin(n);
	for (uint i=0; i<n; i++) {
		water_fx->BeginPass(i);
		water_back_vb->DrawAllTris();
		water_fx->EndPass();
	}
	water_fx->End();

	scene->rtm->CopyColorFrameData( back_color, frame.color, true );
	scene->rtm->CopyDepthFrameData( back_depth, frame.depth );

	//
	//	water fog :
	//	
	water_fx->SetTechnique("tech_water_fog");
	water_fx->Begin(n);
	for (uint i=0; i<n; i++) {
		water_fx->BeginPass(i);

		scene->vbm->DrawFSQuad();

		water_fx->EndPass();
	}
	water_fx->End();

	//rs()->GetDVScene()->DrawPoint(EPoint(), 1.0, EColor::kGreen + EColor::kRed);
	//rs()->GetDVScene()->DrawPoint(EPoint(42.2, 0, 0), 1.0, EColor::kGreen + EColor::kRed);
}



float2 EFRWater::sample_wave_data ( int x, int y ) const
{
	if (x < 0)				{	x = FFT_N + x % FFT_N;		} 
	if (x >= (int)FFT_N)	{	x = x % FFT_N;				}
	if (y < 0)				{	y = FFT_N + y % FFT_N;		}
	if (y >= (int)FFT_N)	{	y = y % FFT_N;				}
		
	float2 h1 = wave_data[ y * FFT_N + x ];
	float2 h0 = wave_data_old[ y * FFT_N + x ];
	float v = ( h1.x - h0.x ) / wavegen.delta_time;
	h1.y = v;
	return	h1;
}

float2 EFRWater::sample_wave_data_f (float x, float y ) const
{
	x -= 0.5f;
	y -= 0.5f;

	float	fx	=	x - floorf( x );	
	float	fy	=	y - floorf( y );	
	
	int ix = (int)floor( x );
	int iy = (int)floor( y );

	float2 s00	= sample_wave_data( ix+0, iy+0 );
	float2 s01	= sample_wave_data( ix+0, iy+1 );
	float2 s11	= sample_wave_data( ix+1, iy+1 );
	float2 s10	= sample_wave_data( ix+1, iy+0 );

	float2 s0x	= lerp( s00, s01, fy );	
	float2 s1x	= lerp( s10, s11, fy );	
										    
	float2 s	= lerp( s0x, s1x, fx );

	return s;
}


float2 sine_wave(float t, float x, float w)
{
	float2 sm;
	sm.x = 0.5 * sin(w * t + w*w/G * x);
	sm.y = 0;
	return sm;
}


//
//	EFRWater::GetWaterDynamics
//
void EFRWater::GetWaterDynamics( const EPoint &pos, float &pressure, float &surface_level, EVector &velocity ) const
{
	pressure		=	0;
	velocity		=	EVector::kZero;
	surface_level	=	0;
	
	float	x	=	( pos.x / spatial_period * FFT_N);	
	float	y	=	( pos.y / spatial_period * FFT_N);	
	
	//float2		sm	=	sine_wave( wavegen.time, pos.x, 2.24 );
	float2	sm		=	{0,0};
	
	if (peak_frequency < 9.9 ) {
		sm			=	sample_wave_data_f( x, y );
	}
	surface_level	=	sm.x;
	velocity.z		=	sm.y;
	//velocity.x		=	sm.r;	//	fake for broaching
	
	if (pos.z<surface_level) {
		float	d	=	-pos.z;
		pressure	=	( d + surface_level ) * G * 1000.0f;
	}
}  



/*-----------------------------------------------------------------------------
	Wakes :
-----------------------------------------------------------------------------*/


void EFRWater::init_wakes ( void )
{
	CUDA_CALL( cudaMalloc( (void**)&d_drop_pool,  sizeof(WakeDrop) * DROP_POOL_SIZE ) );

	cur_wake_buf = 0;
	const int SIZE = WAKE_GRID_SIZE;
	wake_buf_cuda.resize(2);
	for (int i = 0; i < wake_buf_cuda.size(); ++i)
	{
		WakeBufInfo & bi = wake_buf_cuda[i];
		bi.size = SIZE;
		CUDA_CALL( cudaMallocPitch( (void**)&bi.d_ptr, &bi.pitch, sizeof(float) * SIZE, SIZE ) );
		CUDA_CALL( cudaMemset2D( (void*)bi.d_ptr, bi.pitch, 0, sizeof(float) * SIZE, SIZE ) );

		wakes_tex[i]	=	scene->txm->CreateTexture2D( SIZE, SIZE, RS_FORMAT_R_F32 );
		GLuint wake_tex_handle = wakes_tex[i].as<EGLTexture>()->GetTexObject();
		CUDA_CALL( cudaGraphicsGLRegisterImage(&wakes_tex_cuda[i], wake_tex_handle , GL_TEXTURE_2D, cudaGraphicsMapFlagsWriteDiscard ) );
	}
	CUDA_CALL( cudaThreadSynchronize() );
} 

void EFRWater::kill_wakes ( void )
{
	for (int i = 0; i < wake_buf_cuda.size(); ++i)
	{
		CUDA_CALL( cudaFree( wake_buf_cuda[i].d_ptr ));
		CUDA_CALL( cudaGraphicsUnregisterResource(wakes_tex_cuda[i]) );
		wakes_tex_cuda[i] = NULL;
	}
	wake_buf_cuda.clear();

	CUDA_CALL( cudaFree( d_drop_pool ) );
	cudaThreadSynchronize();
}

void EFRWater::update_wakes ( void )
{
	//drop_accum.clear();
	//return;

	PROFILE_FUNCTION( "update_wakes" );

	// add drops
	wake_center = EPoint();
	for (int i = 0; i < drop_accum.size(); ++i)
	{
		float3 p = drop_accum[i].pos;
		wake_center += EVector(p.x, p.y);
	}
	wake_center = wake_center * (1.0f / drop_accum.size());

	for (int i = 0; i < drop_accum.size(); i += DROP_POOL_SIZE )
	{
		int drop_n = min(drop_accum.size() - i, DROP_POOL_SIZE);
		cudaMemcpy(d_drop_pool, &drop_accum[i], drop_n*sizeof(WakeDrop), cudaMemcpyHostToDevice);
		cudaAddWakeDropsKernel(drop_n, d_drop_pool, wake_buf_cuda[cur_wake_buf], WAKE_GRID_STEP);
	}
	
	cudaError err =  cudaThreadSynchronize();
	if (err != cudaSuccess)
	{
		LOGF("cuda error %d\n", err);
	}

	drop_accum.clear();

	// simulate
	cur_wake_buf = 1 - cur_wake_buf;
	cudaSimualteWakeKernel(WAKE_GRID_STEP, &wake_buf_cuda[0], cur_wake_buf);

	err = cudaThreadSynchronize();
	if (err != cudaSuccess)
	{
		LOGF("cuda error %d\n", err);
	}

	// update wake texture
	CUDA_CALL( cudaGraphicsMapResources(1, &wakes_tex_cuda[cur_wake_buf], 0) );

	cudaArray * wake_array = NULL;
	CUDA_CALL( err = cudaGraphicsSubResourceGetMappedArray(&wake_array, wakes_tex_cuda[cur_wake_buf], 0, 0) ); 
	if (err != cudaSuccess)
	{
		LOGF("cuda error %d\n", err);
	}


	const WakeBufInfo &wb = wake_buf_cuda[cur_wake_buf];

	CUDA_CALL( cudaMemcpy2DToArray(wake_array, 0, 0, 
		wb.d_ptr, wb.pitch, wb.size*sizeof(float), wb.size,  // src
		cudaMemcpyDeviceToDevice ) );


	CUDA_CALL( cudaGraphicsUnmapResources(1, &wakes_tex_cuda[cur_wake_buf], 0) );

	GLuint wake_tex_handle = wakes_tex[cur_wake_buf].as<EGLTexture>()->GetTexObject();
	GL_CALL( glBindTexture(GL_TEXTURE_2D, wake_tex_handle) );
	GL_CALL( glGenerateMipmap(GL_TEXTURE_2D); );
	GL_CALL( glBindTexture(GL_TEXTURE_2D, 0) );
}

//
//	EFRWater::MakeWaterDrop
//
void EFRWater::MakeWaterDrop( const EPoint &pos, float offset )
{
	float ofs = WAKE_DROP_INTENSITY * offset*powf(EMath::Randf(), WAKE_DROP_POW);
	WakeDrop drop = { make_float3(pos.x, pos.y, pos.z), ofs };
	drop_accum.push_back(drop);
}



/*-----------------------------------------------------------------------------
	FFT WATER :
-----------------------------------------------------------------------------*/


//
//	EFRWater::init_cuda_fft_water
//
void EFRWater::init_cuda_fft_water( void )
{
	LOG_INIT("CUDA stuff");
    //CUDA_CALL( cudaGLSetGLDevice( cutGetMaxGflopsDeviceId() ) );
	CUDA_CALL( cudaGLSetGLDevice( 0 ) );
	
	GL_CALL( glGenBuffers(1, &wave_buf_gl) ); 
	GL_CALL( glBindBuffer(GL_ARRAY_BUFFER, wave_buf_gl) ); 
	unsigned int size = FFT_N * FFT_N * sizeof(cufftComplex) ; 
	GL_CALL( glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW) ); 
	GL_CALL( glBindBuffer(GL_ARRAY_BUFFER, 0) ); 
	
	CUDA_CALL( cudaGraphicsGLRegisterBuffer(&wave_buf_cuda, wave_buf_gl, cudaGraphicsMapFlagsWriteDiscard) );

	idata	=	idatat	=	NULL;

	CUDA_CALL( cudaMalloc( (void**)&idata,  sizeof(cufftComplex) * FFT_N * FFT_N ) );
	CUDA_CALL( cudaMalloc( (void**)&idatat, sizeof(cufftComplex) * FFT_N * FFT_N ) );
    
	cufftPlan2d( &plan, FFT_N, FFT_N, CUFFT_C2C );

	waves_tex	=	scene->txm->CreateTextureF4( FFT_N, FFT_N, NULL );

	wave_data.assign(FFT_N * FFT_N, make_float2(0.0, 0.0));
	wave_data_old.assign(FFT_N * FFT_N, make_float2(0.0, 0.0));
	
	create_init_spectra();

	init_wakes();
	
	update_cuda_fft_water();
	update_cuda_fft_water();

	dump_time = scene->common.time;
}


//
//	EFRWater::kill_cuda_fft_water
//
void EFRWater::kill_cuda_fft_water( void )
{
	kill_wakes();

	CUDA_CALL( cudaGraphicsUnregisterResource(wave_buf_cuda) );
	glDeleteBuffers(1, &wave_buf_gl);

	cufftDestroy(plan);
	CUDA_CALL( cudaFree(idata) );
	CUDA_CALL( cudaFree(idatat) );
	CUDA_CALL( cudaThreadExit() );
}


template <class T>
void DumpGPUMem(size_t n, const T *d_ptr, const char * fn)
{
	IHrFile f = fs()->openFile(fn, FS_OPEN_WRITE);
	std::vector<T> buf(n);

	CUDA_CALL( cudaMemcpy(&buf[0], d_ptr, buf.size() * sizeof(T), cudaMemcpyDeviceToHost) );
	f->write(&buf[0], buf.size() * sizeof(T));
}


//
//	EFRWater::update_cuda_fft_water
//
void EFRWater::update_cuda_fft_water( void  )
{
	wave_data_old	=	wave_data;

	//PROFILE_FUNCTION( "update_cuda_fft_water" );
	cudaGenerateSpectrumKernel( idata, idatat, FFT_N, FFT_N, wavegen.time, dk );
	cudaError err = cudaThreadSynchronize();
	if (err != cudaSuccess)
	{
		LOGF("cuda error %d\n", err);
	}

	CUDA_CALL( cudaGraphicsMapResources(1, &wave_buf_cuda, 0) );
	cudaThreadSynchronize();

	size_t size = 0; // sizeof(cufftComplex) * FFT_N * FFT_N
	cufftComplex * d_odata = NULL;
	CUDA_CALL( cudaGraphicsResourceGetMappedPointer((void**)&d_odata, &size, wave_buf_cuda) );
	cudaThreadSynchronize();
		
	cufftExecC2C( plan, idatat, d_odata, CUFFT_INVERSE );
	cudaThreadSynchronize();

	// copy to CPU for ship simulation
	CUDA_CALL( cudaMemcpy(&wave_data[0], d_odata, size, cudaMemcpyDeviceToHost) );

	if (false && scene->common.time - dump_time > 1.0)
	{
		static int iter = 0;
		LOGF("Dump!");
		dump_time = scene->common.time;
		DumpGPUMem(FFT_N * FFT_N, d_odata, va("heightmap%03d.dat", iter));
		++iter;
	}
	
	CUDA_CALL( cudaGraphicsUnmapResources(1, &wave_buf_cuda, 0) );
	cudaThreadSynchronize();

	
	// update wave texture
	GLuint wave_tex_handle = waves_tex.as<EGLTexture>()->GetTexObject();
	GL_CALL( glBindTexture(GL_TEXTURE_2D, wave_tex_handle) );
	GL_CALL( glBindBuffer( GL_PIXEL_UNPACK_BUFFER, wave_buf_gl) );

	GL_CALL( glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, FFT_N, FFT_N, GL_RG, GL_FLOAT, 0) );
	GL_CALL( glGenerateMipmap(GL_TEXTURE_2D); );

	GL_CALL( glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0) );
	GL_CALL( glBindTexture(GL_TEXTURE_2D, 0) );

	// dump
	if (false/*scene->common.time - dump_time > 2.0*/)
	{
		LOGF("Dump!");
		dump_time = scene->common.time;

		const char *fnames[2] = { "wake0.dat", "wake1.dat" };
		for (int i = 0; i < 2; ++i)
		{
			const WakeBufInfo & wb = wake_buf_cuda[i];
			DumpGPUMem(wb.size * wb.size, wb.d_ptr, fnames[i]);
		}
	}

}


float EFRWater::jonswap( float w )
{
	float wmax = peak_frequency;
	float sigma = w <= wmax ? 0.07f : 0.09f;
	
	float dw2	=	(w - wmax) * (w - wmax) ;
	
	float delta	= exp( -dw2 / ( 2 * (sigma*sigma) * (wmax*wmax) ) );
	
 	return pow( abs(jonswap_gamma), delta );
}


float EFRWater::normalize_spectra( void )
{
	float wmax = peak_frequency;
	if (jonswap_gamma>0) {
		return 1;
	}
	float ipm = 0;
	float ijs = 0;
	float dw  = 0.05f;
	for (float w=dw; w<wmax*3; w+=dw) {
		float dpm = dw * 0.0081f*G*G * pow(w,-5) * exp(-1.25 * pow(peak_frequency/w, 4) );
		ipm = ipm + dpm;
		ijs = ijs + dpm * jonswap( w );
	}	
	return ipm / ijs;
}


float EFRWater::spectraPM( float w ) 
{
	float spm = 0.0081f*G*G * pow(w,-5) * exp(-1.25 * pow(peak_frequency/w, 4) );
	return spm * jonswap( w ) * spectra_norm;
}



float EFRWater::spectraPM_k( float k ) 
{
	if (k==0) return 0;
    float w = sqrt( k*G );
    return 0.5 * spectraPM(w) * G / w;
}


float EFRWater::distrib_integral( void ) 
{
	float dx = PI / 128.0f;
	float integral = 0;
	for (float x=-PI/2.0; x<PI/2; x+=dx) {
		integral += pow(cos(x), narrowness);
	}
	integral *= dx;
	return integral;
}


float EFRWater::amplitude_2d( float kx, float ky )
{
    float k	 = sqrt(kx*kx + ky*ky);
    float c	 = max(0.0f, kx / k);
    
    if (k==0) { return 0; }

	float e = spectraPM_k(k) * pow(c, narrowness) / k;
    
    return sqrt( 2 * e * (dk*dk) );
}


float gauss( void ) {
	float u1 = rand() / (float)RAND_MAX;
	float u2 = rand() / (float)RAND_MAX;
	if (u1 < 1e-6f)
		u1 = 1e-6f;
	return sqrtf(-2 * logf(u1)) * cosf(2*PI * u2);
}


//
//	EFRWater::create_init_spectra
//
void EFRWater::create_init_spectra( void )
{
	w_cutoff		=	clamp<float>(8 * peak_frequency, 2, 12);
	k_cutoff		=	w_cutoff * w_cutoff / G;
	dk				=	k_cutoff / (FFT_N/2);
	spatial_period	=	2 * PI / dk;
	
    float di = distrib_integral();
    
    LOGF("FFT water : grid size                : %d", FFT_N	 );
    LOGF("            peak frequency           : %f", peak_frequency );
    LOGF("            cutoff frequency         : %f", w_cutoff );
    LOGF("            cutoff wave number       : %f", k_cutoff );
    LOGF("            delta wave number        : %f", dk );
    LOGF("            spatial period           : %f", spatial_period );
    LOGF("            asimuthal distr integral : %f", di );
    
	ERandMT	randmt(water_seed);    


	spectra_norm	=	normalize_spectra();
	
	

	std::vector<float2> idata_h(FFT_N * FFT_N);

	for (uint i=0; i<FFT_N; i++) {
		for (uint j=0; j<FFT_N; j++) {
		
			float	kx	=	1 * (float)j * dk;
			float	ky	=	1 * (float)i * dk;

			if (j > FFT_N/2/**/) {
				kx -= 2*k_cutoff;
			}
			
			if (i > FFT_N/2/**/) {
				ky -= 2*k_cutoff;
			}
			
			float	a	=	amplitude_2d( kx, ky ) / sqrt( di );
			float	p	=	randmt.randomMTf() * 2 * PI;//EMath::Randf( 0, 2*PI );

			idata_h[i * FFT_N + j].x	=	a * cos(p);
			idata_h[i * FFT_N + j].y	=	a * sin(p);
		}
	}

	//for (uint i=0; i<FFT_N; i++) {
	//	for (uint j=0; j<FFT_N; j++) {
	//		idata_h[i][j].x	=	0;
	//		idata_h[i][j].y	=	0;
	//	}
	//}
	//idata_h[0][FFT_N/8].x = 1;
	//idata_h[0][FFT_N/8].y = 0;
	
	CUDA_CALL( cudaMemcpy(idata, &idata_h[0], sizeof(cufftComplex) * FFT_N * FFT_N, cudaMemcpyHostToDevice) );
}


/*-----------------------------------------------------------------------------
	Lua API :
-----------------------------------------------------------------------------*/

int EFRWater::set_spectra( lua_State *L )
{
	LUA_INTERFACE( L );

	if (!self) { return 0; }
	
	self->peak_frequency	=	lua.RequireNumber( 1, "peak frequency" );
	self->narrowness		=	lua.RequireNumber( 2, "narrowness" );
	
	if (lua_gettop(L)>=3) {
		self->jonswap_gamma	=	lua.RequireNumber( 3, "JONSWAP gamma");
	} else {
		self->jonswap_gamma	=	1;
	}

	if (lua_gettop(L)>=4) {
		self->water_seed = lua.RequireNumber( 4, "Water seed");
	} else {
		self->water_seed = 1;
	}
	
	self->create_init_spectra();
	
	return 0;
}


int EFRWater::set_sine( lua_State *L )
{
	return 0;
}


int EFRWater::set_spectra_wind( lua_State *L )
{
	return 0;
}

int EFRWater::get_wave_height( lua_State *L )
{
	LUA_INTERFACE( L );
	if (!self) { return 0; }
	
	float p;
	float s;
	float t = self->wavegen.time;
	EVector v;
	self->GetWaterDynamics(EPoint::kOrigin, p, s, v);

	lua_pushnumber(L, t);
	lua_pushnumber(L, s);
	
	lua.SetStackGrow(2);
	
	return 2;
}














